package com.sqtracker.recommender.algorithm;

import com.sqtracker.recommender.dto.RecommendationResponse;
import com.sqtracker.recommender.model.TorrentMetadataExt;
import com.sqtracker.recommender.model.UserPreference;
import com.sqtracker.recommender.repository.TorrentMetadataExtRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 混合推荐算法实现类
 *
 * 该算法通过融合多种推荐算法的结果来提供更准确、更多样化的推荐。
 * 混合推荐是现代推荐系统的核心思想，能够充分发挥不同算法的优势，
 * 同时弥补单一算法的不足。
 *
 * 支持的推荐算法类型：
 * 1. ItemCF (基于物品的协同过滤)：擅长发现相似物品
 * 2. ContentBased (基于内容的推荐)：基于用户兴趣特征
 * 3. Exploration (探索性推荐)：提供新颖性和多样性
 *
 * 融合策略：
 * 1. 加权融合：为不同算法分配不同权重
 * 2. 显式偏好增强：对符合用户显式偏好的物品进行额外加权
 * 3. 去重合并：合并来自不同算法的相同物品推荐
 * 4. 综合排序：基于最终分数进行全局排序
 *
 * 默认权重分配：
 * - ItemCF: 40%（协同过滤通常效果较好）
 * - ContentBased: 30%（基于内容特征匹配）
 * - Exploration: 30%（保证推荐多样性）
 *
 * @author sqtracker
 * @version 1.0
 */
public class HybridRecommender {

    private static final Logger logger = LoggerFactory.getLogger(HybridRecommender.class);

    /**
     * 各算法的权重配置
     * key: 算法名称 (item-cf, content-based, exploration)
     * value: 对应权重 (0.0-1.0)
     */
    private final Map<String, Double> algorithmWeights;

    /**
     * Torrent元数据仓库，用于获取物品详细信息
     */
    private final TorrentMetadataExtRepository torrentMetadataExtRepository;

    /**
     * 显式偏好增强因子
     * 当推荐物品符合用户显式偏好时，将分数乘以此因子进行增强
     */
    private final double explicitPreferenceBoostFactor;

    /**
     * 构造函数
     *
     * @param algorithmWeights 各算法权重配置
     * @param torrentMetadataExtRepository Torrent元数据仓库
     * @param explicitPreferenceBoostFactor 显式偏好增强因子
     */
    public HybridRecommender(Map<String, Double> algorithmWeights,
                             TorrentMetadataExtRepository torrentMetadataExtRepository,
                             double explicitPreferenceBoostFactor) {
        this.algorithmWeights = algorithmWeights;
        this.torrentMetadataExtRepository = torrentMetadataExtRepository;
        this.explicitPreferenceBoostFactor = explicitPreferenceBoostFactor;
        logger.info("HybridRecommender initialized with boost factor: {}", explicitPreferenceBoostFactor);
    }

    /**
     * 混合推荐主方法
     *
     * 算法流程：
     * 1. 收集各子算法的推荐结果
     * 2. 按权重对各算法结果进行加权融合
     * 3. 应用显式偏好增强（如果用户有显式偏好设置）
     * 4. 按最终分数排序并返回结果
     *
     * @param itemCFResponse ItemCF算法的推荐结果
     * @param contentBasedResponse 基于内容算法的推荐结果
     * @param explorationResponse 探索算法的推荐结果
     * @param userPreferenceOpt 用户偏好设置（可选）
     * @param userId 用户ID（用于日志）
     * @return 融合后的推荐结果
     */
    public RecommendationResponse recommend(
            RecommendationResponse itemCFResponse,
            RecommendationResponse contentBasedResponse,
            RecommendationResponse explorationResponse,
            Optional<UserPreference> userPreferenceOpt,
            String userId) {

        logger.debug("User {} - Hybrid Recommender started. ItemCF items: {}, ContentBased items: {}, Exploration items: {}",
                userId,
                itemCFResponse.getRecommendations() != null ? itemCFResponse.getRecommendations().size() : 0,
                contentBasedResponse.getRecommendations() != null ? contentBasedResponse.getRecommendations().size() : 0,
                explorationResponse.getRecommendations() != null ? explorationResponse.getRecommendations().size() : 0);

        // 步骤1：初始化推荐结果容器
        // key: torrentId, value: 推荐条目信息（包含综合分数、算法来源、推荐理由等）
        Map<String, Map<String, Object>> recommendationsMap = new HashMap<>();

        // 步骤2：加权融合各算法的推荐结果

        // 融合ItemCF推荐结果
        double itemCFWeight = algorithmWeights.getOrDefault("item-cf", 0.4);
        addWeightedRecommendations(itemCFResponse.getRecommendations(), recommendationsMap, itemCFWeight, "item-cf", userId);

        // 融合ContentBased推荐结果
        double contentBasedWeight = algorithmWeights.getOrDefault("content-based", 0.3);
        addWeightedRecommendations(contentBasedResponse.getRecommendations(), recommendationsMap, contentBasedWeight, "content-based", userId);

        // 融合Exploration推荐结果
        double explorationWeight = algorithmWeights.getOrDefault("exploration", 0.3);
        addWeightedRecommendations(explorationResponse.getRecommendations(), recommendationsMap, explorationWeight, "exploration", userId);

        logger.debug("User {} - After initial weighted aggregation, recommendationsMap size: {}", userId, recommendationsMap.size());

        // 步骤3：应用显式偏好增强
        userPreferenceOpt.ifPresent(prefs -> {
            UserPreference.ExplicitPreferences explicitPrefs = prefs.getExplicitPreferences();
            if (explicitPrefs != null &&
                    ((explicitPrefs.getCategories() != null && !explicitPrefs.getCategories().isEmpty()) ||
                            (explicitPrefs.getTags() != null && !explicitPrefs.getTags().isEmpty()) ||
                            (explicitPrefs.getLanguages() != null && !explicitPrefs.getLanguages().isEmpty()))) {
                logger.info("User {} - Applying explicit preference boost (factor: {}).", userId, explicitPreferenceBoostFactor);
                applyExplicitPreferenceBoost(recommendationsMap, prefs, userId);
            } else {
                logger.debug("User {} - No explicit preferences found or preferences are empty. Skipping explicit preference boost.", userId);
            }
        });

        // 步骤4：按最终分数排序
        List<Map.Entry<String, Map<String, Object>>> sortedRecommendations = recommendationsMap.entrySet()
                .stream()
                .sorted((e1, e2) -> {
                    Double score1 = (Double) e1.getValue().get("score");
                    Double score2 = (Double) e2.getValue().get("score");
                    return Double.compare(score2, score1); // 降序排列
                })
                .collect(Collectors.toList());
        logger.debug("User {} - Sorted recommendations size: {}", userId, sortedRecommendations.size());

        // 步骤5：构建最终推荐结果
        List<Map<String, Object>> finalRecommendations = sortedRecommendations.stream()
                .map(Map.Entry::getValue)
                .collect(Collectors.toList());

        RecommendationResponse response = new RecommendationResponse();
        response.setRecommendations(finalRecommendations);
        logger.info("User {} - Hybrid Recommender finished. Final recommendations count: {}", userId, finalRecommendations.size());

        return response;
    }

    /**
     * 将单个算法的推荐结果添加到融合结果中
     *
     * 对于已存在的物品，累加其分数并合并算法来源信息
     * 对于新物品，直接添加到融合结果中
     *
     * @param recommendations 单个算法的推荐结果列表
     * @param recommendationsMap 融合结果容器
     * @param weight 当前算法的权重
     * @param originalAlgorithmName 算法名称
     * @param userId 用户ID（用于日志）
     */
    private void addWeightedRecommendations(
            List<Map<String, Object>> recommendations,
            Map<String, Map<String, Object>> recommendationsMap,
            double weight,
            String originalAlgorithmName,
            String userId) {

        if (recommendations == null || recommendations.isEmpty()) {
            logger.debug("User {} - No recommendations from algorithm {} to add.", userId, originalAlgorithmName);
            return;
        }

        logger.debug("User {} - Adding {} recommendations from {} with weight {}.", userId, recommendations.size(), originalAlgorithmName, weight);

        for (Map<String, Object> recommendation : recommendations) {
            String torrentId = (String) recommendation.get("torrentId");
            if (torrentId == null) {
                logger.warn("User {} - Found recommendation with null torrentId from algorithm {}. Skipping.", userId, originalAlgorithmName);
                continue;
            }

            // 计算加权分数
            double originalScore = recommendation.get("score") instanceof Number ? ((Number) recommendation.get("score")).doubleValue() : 0.0;
            double weightedScore = originalScore * weight;
            String algorithm = (String) recommendation.get("algorithm");
            String reason = (String) recommendation.get("reason");

            if (recommendationsMap.containsKey(torrentId)) {
                // 物品已存在，累加分数并合并算法来源信息
                Map<String, Object> existingRecommendation = recommendationsMap.get(torrentId);
                double existingScore = (double) existingRecommendation.get("score");
                existingRecommendation.put("score", existingScore + weightedScore);

                String existingAlgorithm = (String) existingRecommendation.get("algorithm");
                String existingReason = (String) existingRecommendation.get("reason");

                // 合并算法来源信息
                if (!existingAlgorithm.contains(algorithm)) {
                    existingRecommendation.put("algorithm", existingAlgorithm + "," + algorithm);
                }
                // 合并推荐理由
                if (reason != null && !existingReason.contains(reason)) {
                    existingRecommendation.put("reason", existingReason + "; " + reason);
                }
                logger.trace("User {} - Merged torrentId {} from {}. New score: {}. Algorithms: {}.", userId, torrentId, algorithm, existingRecommendation.get("score"), existingRecommendation.get("algorithm"));
            } else {
                // 新物品，直接添加
                Map<String, Object> newRecommendation = new HashMap<>();
                newRecommendation.put("torrentId", torrentId);
                newRecommendation.put("score", weightedScore);
                newRecommendation.put("algorithm", algorithm);
                newRecommendation.put("reason", reason);

                recommendationsMap.put(torrentId, newRecommendation);
                logger.trace("User {} - Added new torrentId {} from {}. Score: {}.", userId, torrentId, algorithm, weightedScore);
            }
        }
    }

    /**
     * 应用显式偏好增强
     *
     * 对于符合用户显式偏好的推荐物品，将其分数乘以增强因子，
     * 从而提高这些物品在最终推荐列表中的排名。
     *
     * 匹配规则：
     * - 分类匹配：物品分类在用户偏好分类列表中
     * - 标签匹配：物品标签与用户偏好标签有交集
     * - 语言匹配：物品语言在用户偏好语言列表中
     *
     * @param recommendationsMap 推荐结果容器
     * @param preferences 用户偏好设置
     * @param userId 用户ID（用于日志）
     */
    private void applyExplicitPreferenceBoost(Map<String, Map<String, Object>> recommendationsMap, UserPreference preferences, String userId) {
        UserPreference.ExplicitPreferences explicitPrefs = preferences.getExplicitPreferences();
        if (explicitPrefs == null) {
            logger.debug("User {} - Explicit preferences object is null, cannot apply boost.", userId);
            return;
        }

        // 提取用户的显式偏好
        Set<String> preferredCategories = explicitPrefs.getCategories() == null ? Collections.emptySet() : new HashSet<>(explicitPrefs.getCategories());
        Set<String> preferredTags = explicitPrefs.getTags() == null ? Collections.emptySet() : new HashSet<>(explicitPrefs.getTags());
        Set<String> preferredLanguages = explicitPrefs.getLanguages() == null ? Collections.emptySet() : new HashSet<>(explicitPrefs.getLanguages());

        if (preferredCategories.isEmpty() && preferredTags.isEmpty() && preferredLanguages.isEmpty()) {
            logger.debug("User {} - All explicit preference lists (categories, tags, languages) are empty. No boost to apply.", userId);
            return;
        }

        int boostAppliedCount = 0;
        for (Map.Entry<String, Map<String, Object>> mapEntry : recommendationsMap.entrySet()) {
            String torrentId = mapEntry.getKey();
            Map<String, Object> recommendation = mapEntry.getValue();
            double currentScore = (double) recommendation.get("score");

            // 获取物品的详细信息
            Optional<TorrentMetadataExt> torrentOpt = torrentMetadataExtRepository.findByTorrentId(torrentId);
            if (torrentOpt.isPresent()) {
                TorrentMetadataExt torrent = torrentOpt.get();
                boolean matchesExplicitPreference = false;
                StringBuilder matchReason = new StringBuilder();

                // 检查分类匹配
                if (torrent.getTorrentCategoryId() != null && preferredCategories.contains(torrent.getTorrentCategoryId())) {
                    matchesExplicitPreference = true;
                    matchReason.append("CategoryMatch(").append(torrent.getTorrentCategoryId()).append(");");
                }

                // 检查标签匹配
                if (torrent.getTags() != null && !preferredTags.isEmpty()) {
                    for (String tag : torrent.getTags()) {
                        if (preferredTags.contains(tag)) {
                            matchesExplicitPreference = true;
                            matchReason.append("TagMatch(").append(tag).append(");");
                        }
                    }
                }

                // 检查语言匹配
                if (torrent.getLanguage() != null && preferredLanguages.contains(torrent.getLanguage())) {
                    matchesExplicitPreference = true;
                    matchReason.append("LanguageMatch(").append(torrent.getLanguage()).append(");");
                }

                // 如果匹配显式偏好，应用分数增强
                if (matchesExplicitPreference) {
                    double boostedScore = currentScore * explicitPreferenceBoostFactor;
                    recommendation.put("score", boostedScore);

                    // 更新推荐理由，说明应用了偏好增强
                    String currentReason = (String) recommendation.get("reason");
                    recommendation.put("reason", currentReason + " (Boosted: Explicit Pref - " + matchReason.toString() + ")");

                    boostAppliedCount++;
                    logger.debug("User {} - Boosted torrentId {}: old score {}, new score {}. Reason: {}", userId, torrentId, currentScore, boostedScore, matchReason.toString());
                }
            } else {
                logger.warn("User {} - TorrentMetadataExt not found for torrentId {} during boost application. Cannot determine explicit preference match.", userId, torrentId);
            }
        }

        if (boostAppliedCount > 0) {
            logger.info("User {} - Applied explicit preference boost to {} items.", userId, boostAppliedCount);
        } else {
            logger.info("User {} - No items matched explicit preferences for boosting.", userId);
        }
    }
}